﻿#include "stdafx.h"

#include "svgreader.h"
#include "xml.h"

#include <sstream>
#include <regex>
#include <map>

namespace
{
	const std::vector<std::string> _Split(const std::string& str, char delimiter)
	{
		std::istringstream f(str);

		vector<std::string> result;
		std::string s;
		while (getline(f, s, delimiter))
			result.push_back(std::move(s));
		return result;
	}

	const std::string& _GetAttr(const xml::Attrs& attrs, const std::string& key)
	{
		auto iter = attrs.find(key);
		if (iter != attrs.end())
			return iter->second;
		throw SvgReadError(QString("expect attr %1, but not found").arg(key.c_str()));
	}

	double _ToDouble(const std::string& str)
	{
		try
		{
			const double ret = std::stod(str);
			return ret;
		}
		catch (...)
		{
			throw SvgReadError(QString("value %1 is not a double").arg(str.c_str()));
		}
	}

	int _ToInt(const std::string& str)
	{
		try
		{
			const int ret = std::stoi(str);
			return ret;
		}
		catch (...)
		{
			throw SvgReadError(QString("value %1 is not a int").arg(str.c_str()));
		}
	}

	// 字符串的形式是 "rgb(111, 222,23, 0.3)"
	QColor _ReadColor(const std::string& str)
	{
		const std::regex re(R"(rgb\((\d*), (\d*), (\d*), ([\d.]*)\))");
		std::smatch m;
		std::regex_match(str, m, re);
		const int r = _ToInt(m.str(1));
		const int g = _ToInt(m.str(2));
		const int b = _ToInt(m.str(3));
		const double a = _ToDouble(m.str(4));

		return { r, g, b, static_cast<int>(a * 255.0) };
	}

	void _ReadSvgInfo(SvgCanvasInfo& info, const xml::Element& svgElem)
	{
		const xml::Attrs& attrs = svgElem.attrs;
		info.height = _ToDouble(_GetAttr(attrs, "height"));
		info.width = _ToDouble(_GetAttr(attrs, "width"));
	}

	void _ReadBackground(SvgCanvasInfo& info, const xml::Element& backgroundElem)
	{
		if (backgroundElem.name != "rect")
			throw SvgReadError(QString("background type should be rect, but is %1").arg(backgroundElem.name.c_str()));

		const xml::Attrs& attrs = backgroundElem.attrs;
		info.background = _ReadColor(_GetAttr(attrs, "fill"));
	}

	void _ReadPenProp(SvgShape& shape, const xml::Attrs& attrs)
	{
		shape.Pen().setColor(_ReadColor(_GetAttr(attrs, "stroke")));
		shape.Pen().setWidthF(_ToDouble(_GetAttr(attrs, "stroke-width")));
	}

	void _ReadBrushProp(SvgShape& shape, const xml::Attrs& attrs)
	{
		shape.Brush().setColor(_ReadColor(_GetAttr(attrs, "fill")));
	}

	unique_ptr<SvgRect> _ReadRect(const xml::Element& elem)
	{
		ASSERT(elem.name == "rect");
		const xml::Attrs& attrs = elem.attrs;
		const double x = _ToDouble(_GetAttr(attrs, "x"));
		const double y = _ToDouble(_GetAttr(attrs, "y"));
		const double width = _ToDouble(_GetAttr(attrs, "width"));
		const double height = _ToDouble(_GetAttr(attrs, "height"));
		auto rect = std::make_unique<SvgRect>(QRectF(x, y, width, height));
		_ReadPenProp(*rect, attrs);
		_ReadBrushProp(*rect, attrs);

		return rect;
	}

	unique_ptr<SvgLine> _ReadLine(const xml::Element& elem)
	{
		ASSERT(elem.name == "line");
		const xml::Attrs& attrs = elem.attrs;
		const double x1 = _ToDouble(_GetAttr(attrs, "x1"));
		const double y1 = _ToDouble(_GetAttr(attrs, "y1"));
		const double x2 = _ToDouble(_GetAttr(attrs, "x2"));
		const double y2 = _ToDouble(_GetAttr(attrs, "y2"));
		auto line = std::make_unique<SvgLine>(QLineF(x1, y1, x2, y2));
		_ReadPenProp(*line, attrs);
		return line;
	}

	unique_ptr<SvgEllipse> _ReadEllipse(const xml::Element& elem)
	{
		ASSERT(elem.name == "ellipse");
		const xml::Attrs& attrs = elem.attrs;
		const double cx = _ToDouble(_GetAttr(attrs, "cx"));
		const double cy = _ToDouble(_GetAttr(attrs, "cy"));
		const double rx = _ToDouble(_GetAttr(attrs, "rx"));
		const double ry = _ToDouble(_GetAttr(attrs, "ry"));
		auto ellipse = std::make_unique<SvgEllipse>(QRectF(cx - rx, cy - ry, rx * 2, ry * 2));
		_ReadPenProp(*ellipse, attrs);
		_ReadBrushProp(*ellipse, attrs);

		return ellipse;
	}

	unique_ptr<SvgPath> _ReadPath(const xml::Element& elem)
	{
		ASSERT(elem.name == "path");
		const xml::Attrs& attrs = elem.attrs;
		// const std::string& fillRule = _GetAttr(attrs, "fill-rule");
		const std::string& d = _GetAttr(attrs, "d");

		const vector<std::string> points = _Split(d, ' ');

		QPainterPath path;
		for (size_t i = 0; i < points.size() - 2; )
		{
			if (points[i] == "L")
			{
				path.lineTo(_ToDouble(points[i + 1]), _ToDouble(points[i + 2]));
				i += 3;
			}
			else if (points[i] == "M")
			{
				path.moveTo(_ToDouble(points[i + 1]), _ToDouble(points[i + 2]));
				i += 3;
			}
			else if (points[i] == "C")
			{
				if (i + 8 >= points.size())
					throw std::out_of_range("cubic need 3 points");
				path.cubicTo(
					QPointF(_ToDouble(points[i + 1]), _ToDouble(points[i + 2])),
					QPointF(_ToDouble(points[i + 4]), _ToDouble(points[i + 5])),
					QPointF(_ToDouble(points[i + 7]), _ToDouble(points[i + 8]))
				);
				i += 9;
			}
			else
			{
				throw SvgReadError(QString("path d value is not valid, value is %1").arg(d.c_str()));
			}
		}

		path.setFillRule(Qt::OddEvenFill); // todo 根据写的设置
		auto pathShape = std::make_unique<SvgPath>(std::move(path));
		_ReadPenProp(*pathShape, attrs);
		_ReadBrushProp(*pathShape, attrs);
		return pathShape;
	}

	unique_ptr<SvgText> _ReadText(const xml::Element& elem)
	{
		ASSERT(elem.name == "text");
		const xml::Attrs& attrs = elem.attrs;
		const double x = _ToDouble(_GetAttr(attrs, "x"));
		const double y = _ToDouble(_GetAttr(attrs, "y"));
		const QColor fill = _ReadColor(_GetAttr(attrs, "fill"));

		QFont font;
		font.setFamily(QString::fromUtf8(_GetAttr(attrs, "font-family").c_str()));
		font.setPixelSize(_ToInt(_GetAttr(attrs, "font-size")));

		QString content;
		if (!elem.subs.empty())
		{
			const xml::Element& contentElem = elem.subs[0];
			if (contentElem.type != xml::ElemType::Text)
				throw SvgReadError("text content xml element type is not Text");
			content = QString::fromUtf8(contentElem.name.c_str());
		}

		auto text = std::make_unique<SvgText>(QPointF(x, y), font, content);
		text->Pen().setColor(fill);
		return text;
	}

	unique_ptr<SvgPolygon> _ReadPolygon(const xml::Element& elem)
	{
		ASSERT(elem.name == "polygon");
		const xml::Attrs& attrs = elem.attrs;
		// const std::string fillRule = _GetAttr(attrs, "fill-rule");
		const std::string& points = _GetAttr(attrs, "points");
		QPolygonF polygon;

		std::regex reg("[\\d.]+");
		std::smatch m;

		double x = 0;
		int flag = 0;

		for (auto pos = points.begin(); std::regex_search(pos, points.end(), m, reg); pos = m.suffix().first)
		{
			if (flag == 0)
			{
				x = _ToDouble(m.str());
				flag++;
			}
			else if (flag == 1)
			{
				const double y = _ToDouble(m.str());
				polygon.push_back(QPointF(x, y));
				flag = 0;
			}
		}

		auto shape = std::make_unique<SvgPolygon>(polygon);
		_ReadPenProp(*shape, attrs);
		_ReadBrushProp(*shape, attrs);
		return shape;
	}

	unique_ptr<SvgShape> _ReadShape(const xml::Element& shapeElem)
	{
		using ShapeReaderType = std::function<unique_ptr<SvgShape>(const xml::Element& elem)>;
		static const std::map<std::string, ShapeReaderType> shapeReaderMap{
			{"rect", _ReadRect},
			{"line", _ReadLine},
			{"path", _ReadPath},
			{"ellipse", _ReadEllipse},
			{"polygon", _ReadPolygon},
			{"text", _ReadText} };

		const auto iter = shapeReaderMap.find(shapeElem.name);
		if (iter != shapeReaderMap.end())
			return iter->second(shapeElem);
		throw SvgReadError(QString("can not support type %1 shape").arg(shapeElem.name.c_str()));
	}

	void _ReadShapes(SvgCanvasInfo& info, const xml::Elements& shapeElems)
	{
		size_t i = 0;
		while (shapeElems[i].type == xml::ElemType::Text)
			i++;
		_ReadBackground(info, shapeElems[i++]);

		for (; i < shapeElems.size(); ++i)
		{
			if (shapeElems[i].type == xml::ElemType::Text)
				continue;
			info.shapes.push_back(_ReadShape(shapeElems[i]));
		}
	}
}

SvgCanvasInfo ReadSvg(const xml::Elements& elems)
{
	ASSERT(!elems.empty() && elems[0].name == "svg");

	const xml::Element& svgElem = elems[0];
	SvgCanvasInfo info;

	_ReadSvgInfo(info, svgElem);
	if (svgElem.subs.empty())
		return info;

	_ReadShapes(info, svgElem.subs);

	return info;
}

